
#include "exhash_tables.h"

#include <assert.h>
#include <string.h>

#include "common/alloc.h"

static struct ExHashTable * new_table()
{
    struct ExHashTable * e = xmalloc(sizeof(struct ExHashTable));
    int i;
    for (i = 0; i < EX_HASH_ARR_SIZE; ++i)
    {
        e->arr[i] = EX_HASH_INT_MAX;
    }
    e->next = NULL;
    return e;
}


void free_ex_hash_calc_tables(struct ExHashCalcTables * etables)
{
    if (etables == NULL)
    {
        return;
    }

    // dispose sums
    xfree(etables->sums);

    // dispose table entries
    struct ExHashTable * e = etables->etables;
    while (e != NULL)
    {
        struct ExHashTable * next = e->next;
        xfree(e);
        e = next;
    }

    // dispose struct itself
    xfree(etables);
}

struct ExHashCalcTables * create_ex_hash_tables(const char ** strs, size_t strs_count)
{
    struct ExHashCalcTables * result = xmalloc(sizeof(struct ExHashCalcTables));
    struct ExHashTable * ent = NULL;
    ExHashInt increment; // hash increment coefficient
    size_t * str_lens;
    size_t len_index;
    size_t str_index;

    result->etables = NULL;
    result->sums = NULL;
    result->max_str_len = 0;

    // retrieve str lens and max str len and initialize final sums
    result->sums = xmalloc(strs_count * sizeof(ExHashInt));
    str_lens = xmalloc(strs_count * sizeof(size_t));
    for (str_index = 0; str_index < strs_count; ++str_index)
    {
        size_t s = strlen(strs[str_index]);
        str_lens[str_index] = s;

        // init sum
        result->sums[str_index] = 0;

        // get max str len
        if (s > result->max_str_len)
        {
            result->max_str_len = s;
        }
    }

    // initialize increment coefficient
    increment = 1;

    // for each length
    for (len_index = 0; len_index < result->max_str_len; ++len_index)
    {
        // initialize hash offset coefficient
        ExHashInt offset = (len_index == 0 ? -1 : 0);
        // save current entry
        struct ExHashTable * prev_ent = ent;

        ent = new_table();

        if (prev_ent)
        {
            prev_ent->next = ent;
        }
        else
        {
            assert(result->etables == NULL);
            // initialize result
            result->etables = ent;
        }

        // for each str
        for (str_index = 0; str_index < strs_count; ++str_index)
        {
            size_t cur_str_len = str_lens[str_index];
            if (len_index >= cur_str_len)
            {
                continue;
            }

            unsigned char ch = (unsigned char)strs[str_index][len_index];
            if (ent->arr[ch] == EX_HASH_INT_MAX)
            {
                offset += increment;
                if (offset >= EX_HASH_MAX_INT_GUARD)
                {
                    // hash table is about to be overrun
                    free_ex_hash_calc_tables(result);
                    result = NULL;
                    goto End;
                }

                ent->arr[ch] = offset;
            }

            // accumulate sum to get final hash
            result->sums[str_index] += ent->arr[ch];
        }

        // get new increment
        increment = offset + 1;
    }

End:
    xfree(str_lens);
    return result;
}
